/*
 * Copyright 2004-2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
 * Distributed under the terms of the MIT License.
 */


/**	This file contains the boot floppy and BFS boot block entry points for
 *	the stage 2 boot loader.
 *	The floppy entry point is at offset 0. It's loaded at 0x07c0:0x000. It
 *	will load the rest of the loader to 0x1000:0x0200 and execute it.
 *	The BFS boot block will load the whole stage 2 loader to 0x1000:0x0000
 *	and will then jump to 0x1000:0x0200 as its entry point.
 *	This code will then switch to protected mode and will directly call
 *	the entry function of the embedded ELF part of the loader.
 */

#include "multiboot.h"

#define GLOBAL(x) .globl x ; x

#define OUR_MB_FLAGS (MULTIBOOT_PAGE_ALIGN \
	| MULTIBOOT_MEMORY_INFO \
	/*| MULTIBOOT_VIDEO_MODE*/ \
	| MULTIBOOT_AOUT_KLUDGE)

// load address
#define LOAD_SEGMENT 0x1000
#define LOAD_ADDRESS 0x10000

// MultiBoot load address
#define MB_LOAD_ADDRESS 0x100000
//#define MB_LOAD_ADDRESS LOAD_ADDRESS
#define MB_LOAD_OFFSET (MB_LOAD_ADDRESS - LOAD_ADDRESS)

// this saves us some trouble with relocation (I didn't manage GAS to
// create 32 bit references to labels)
#define FAILURE_STRING 0x1d0
#define DOT_STRING 0x1fc

#define DRIVE_RETRIES 3
	// when the drive reading fails for some reason, it will
	// retry this many times until it will report a failure

.text
.code16

/** This is the entry point when we were written directly to a floppy disk */

	jmp		floppy_start

sNumSectors:
	// this location will contain the length of the boot loader as
	// written by the "makeflop" command in 512 byte blocks
	// 0x180 is the allowed maximum, as the zipped TAR with the
	// kernel and the boot module might start at offset 192 kB
	.word BOOT_ARCHIVE_IMAGE_OFFSET*2

floppy_start:
	cli
	cld

	// set up the stack to 0x0000:0x9000
	xor		%ax, %ax
	mov		%ax, %ss
	mov		$0x9000, %sp

	push	$0x07c0
	pop		%ds
	push	$0x1000
	pop		%es

	// load the rest of the boot loader to 0x1000:0x0200
	.code32					// we need to create a 32-bit relocation entry for the linker...
	.byte	0x67
	movw	sNumSectors - 0x10000, %di
		// the loader symbols are located at offset 0x10000
	.code16
	xor		%dh, %dh		// head 0, don't change BIOS boot device
	mov		$0x2, %cx		// sector 2
	mov		$0x200, %bx		// to 0x1000:0x0200
	call	load_sectors

	// ToDo: this seems to be problematic, at least under Bochs (reboot will fail)
#if 0
	or		%dl, %dl		// if it's a floppy, turn off its motor
	jnz		start_loader
	call	disable_floppy_motor
#endif

start_loader:
	// indicate that we were booted from CD/floppy/whatever
	.code32
	.byte	0x67
	movb	$1, gBootedFromImage - 0x7c00
		// %ds is 0x7c0 right now, but the symbol were loaded
		// to offset 0x10000
	.code16

	// set our environment and jump to the standard BFS boot block entry point
	xor		%dx, %dx		// boot device ID and partition offset to 0
	xor		%eax, %eax
	ljmp	$0x1000, $0x0200


/**	Loads %di sectors from floppy disk, starting at head %dh, sector %cx.
 *	The data is loaded to %es:%bx. On exit, %es:%bx will point immediately
 *	behind the loaded data, so that you can continue to read in data.
 *	%ax, %cx, %dx, %bp, %di and %si will be clobbered.
 */

load_sectors:
	// first, get information about the drive as we intend to read whole tracks
	push	%bx
	push	%cx
	push	%dx
	push	%di
	push	%es

	movb	$8, %ah			// get drive parameters - changes a lot of registers
	int		$0x13

	pop		%es
	pop		%di
		// ToDo: store the number of heads somewhere (it's in %dh)
	pop		%dx
	and		$63, %cx		// mask out max. sector number (bit 0-5)
	mov		%cx, %si		// and remember it
	pop		%cx
	pop		%bx

load_track:
	mov		%di, %ax		// limit the sector count to track boundaries
	add		%cl, %al
	dec		%ax
	cmp		%si, %ax
	jbe		matches_track_boundary
	mov		%si, %ax
matches_track_boundary:
	inc		%ax				// take the current sector offset into account
	sub		%cl, %al

	// make sure we don't cross a 64kB address boundary or else the read will fail
	// (this small piece of knowledge took me some time to accept :))
	shl		$9, %ax
	mov		%ax, %bp
	add		%bx, %bp
	jnc		respects_boundary
	xor		%ax, %ax		// only read up to the 64kB boundary
	sub		%bx, %ax
respects_boundary:
	shr		$9, %ax
	mov		DRIVE_RETRIES, %bp

try_to_read:
	pusha
	movb	$2, %ah			// load sectors from drive
	int		$0x13
	jnc		read_succeeded

	xor		%ax, %ax
	int		$0x13			// reset drive
	popa

	dec		%bp
	jz		load_failed		// if already retried often enough, bail out
	jmp		try_to_read

read_succeeded:
	mov		$DOT_STRING, %si
	call	print_string
	popa

	xor		%ah, %ah
	add		%ax, %cx		// next sector start
	sub		%ax, %di		// update sectors left to be read

	shl		$9, %ax			// get byte offset
	add		%ax, %bx		// update target address
	jnz		check_sector_start

	mov		%es, %ax		// overflow to the next 64kB, %bx is already zero
	add		$0x1000, %ax
	mov		%ax, %es

check_sector_start:
	mov		%si, %ax		// compare the sectors, not the cylinders
	cmp		%al, %cl
	jbe		continue_reading

	sub		%si, %cx
	inc		%dh				// next head
	cmp		$1, %dh
		// ToDo: check max. number of heads!
	jbe		check_sector_start

	xor		%dh, %dh		// next cylinder
	inc		%ch
	jmp		check_sector_start

continue_reading:
	or		%di, %di
	jnz		load_track
	ret

load_failed:
	mov		$FAILURE_STRING, %si
	call	print_string

	xor		%ax, %ax
	int		$0x16			// wait for key
	int		$0x19			// and reboot

disable_floppy_motor:
	xor		%al, %al
	mov		$0x3f2, %dx
	out		%al, %dx
	ret

print_string:
	movb	$0x0e, %ah
	xor		%bx, %bx
print_char:
	lodsb
	orb		%al, %al		// are there still characters left?
	jz		no_more_chars
	int		$0x10
	jmp		print_char
no_more_chars:
	ret

floppy_end:
	.org	FAILURE_STRING
	.string " Loading failed! Press key to reboot.\r\n"
	.org	DOT_STRING
	.string	"."

	.org	0x01fe
	.word	0xaa55
		// this bumps the "start" label to offset 0x0200 as
		// expected by the BFS boot loader, and also marks
		// this block as valid boot block for the BIOS


//--------------------------------------------------------------

/**	This is the entry point of the stage2 bootloader when it has
 *	been loaded from the stage1 loader from a BFS disk.
 */

bfs_start:
	cld						// set the data, and extra segment to our code start
	pushw	$0x1000
	pop		%ds
	push	%ds
	pop		%es

	.code32					// save knowledge from the BFS boot block for later use
	.byte	0x67
	movb	%dl, gBootDriveID - 0x10000
	.byte	0x67
	.byte	0x66
	movl	%eax, gBootPartitionOffset - 0x10000
	.code16

	xor		%ax, %ax		// set up stack at 0x0000:0x9000
	mov		%ax, %ss
	mov		$0x9000, %sp

	cli						// no interrupts please
	call	enable_a20		// enable a20 gate

	.code32					// This forces a 32 bit relocation entry
	.byte	0x66			// that allows linking with others
	.byte	0x67
	lgdt	gdt_descriptor - 0x10000
		// load global descriptor table; we're still in real mode segment
		// 0x1000 so we have to manually correct the address

	.code16
	movl	%cr0, %eax		// set the PE bit of cr0 to switch to protected mode
	orb		$0x1, %al
	movl	%eax, %cr0

	.code32
	.byte	0x66
	ljmp	$0x8, $_protected_code_segment
_protected_code_segment:
	mov		$0x10, %ax		// load descriptor 2 in the data and stack segment selectors
	mov		%ax, %ds
	mov		%ax, %es
	mov		%ax, %fs
	mov		%ax, %gs
	mov		%ax, %ss

	movl	$0x10000, %esp	// setup new stack
	pushl	$0				// terminate stack frame chain (next frame and
	pushl	$0				// return address)
	mov		%esp, %ebp

	call	_start

//--------------------------------------------------------------

/** MultiBoot entry point
 */

multiboot_start:
	//subl		$MULTIBOOT_MAGIC2, %eax
	//jnz		load_failed		// rts to grub ?
	movl		%ebx, gMultiBootInfo + MB_LOAD_OFFSET
	// load the GDT
	lgdt		gdt_descriptor + MB_LOAD_OFFSET

#if MB_LOAD_ADDRESS != LOAD_ADDRESS
	// QEMU does not like the real load address...
	// copy ourselves to the expected location
	cld
	mov		$(_end - LOAD_ADDRESS), %ecx
	add		$3, %ecx
	shr		$2, %ecx
	mov		$LOAD_ADDRESS, %edi
	mov		$MB_LOAD_ADDRESS, %esi
	rep movsl

	// reload the GDT just in case
	lgdt		gdt_descriptor
#endif

relocated_mb_start:
	ljmp		$0x8, $_protected_code_segment

//--------------------------------------------------------------

/** Enables the a20 gate. It will first try to enable it through
 *	the BIOS, and, if that fails, will use the old style AT mechanism
 *	using the keyboard port.
 *	ToDo: it no longer does this! Now, it just uses the "fast A20"
 *		mechanism using port 0x92. This does work on all systems
 *		I have access to.
 */

enable_a20:
	inb		$0x92, %al
	testb	$0x02, %al
	jnz		_a20_out
	orb		$0x02, %al
	andb	$0xfe, %al
	outb	%al, $0x92
_a20_out:
	ret

// ToDo: the code below didn't seem to work properly on all machines
/*	movw	$0x2402, %ax		// first, query the a20 status
	int		$0x15
	jc		_a20_old_method		// if that fails, use the old AT method
	test	$0x1, %al
	jnz		_a20_done			// Is a20 gate already enabled?
	movw	$0x2401, %ax
	int		$0x15
	jnc		_a20_done
_a20_old_method:
	call	_a20_loop1			// empty the keyboard buffer
	jnz		_a20_done
	movb	$0xd1, %al
	outb	%al, $0x64
	call	_a20_loop1			// empty the keyboard buffer
	jnz		_a20_done
	movb	$0xdf, %al
	outb	%al, $0x60
_a20_loop1:
	movl	$0x20000, %ecx
_a20_loop2:
	inb		$0x64, %al
	test	$0x2, %al
	loopne	_a20_loop2
_a20_done:
	ret
*/

//--------------------------------------------------------------

.org 856
	// since we don't need the above space when the boot loader is
	// running, it is used as a real mode scratch buffer (as our
	// boot loader spans over the whole real mode 0x1000 segment)

.align 4
multiboot_header:
	.long	MULTIBOOT_MAGIC
	.long	OUR_MB_FLAGS
	.long	(0 - MULTIBOOT_MAGIC - OUR_MB_FLAGS)		// checksum (8 bytes)
	.long	multiboot_header + MB_LOAD_OFFSET
	.long	.text + MB_LOAD_OFFSET
	.long	.bss + (MB_LOAD_OFFSET - 24)
	.long	_end + (MB_LOAD_OFFSET - 24)
	.long	multiboot_start + MB_LOAD_OFFSET
#if (OUR_MB_FLAGS & MULTIBOOT_VIDEO_MODE)
	.long	0	// non text mode
	.long	1024
	.long	786
	.long	24
#endif

/* global data table */

gdt:
	// null descriptor
	.long	0
	.long	0

	// kernel code segment
	.long	0x0000ffff		// base: 0, limit: 4 GB
	.long	0x00cf9e00		// type: 32 bit, exec-only conforming, privilege 0
	// kernel data and stack segment
	.long	0x0000ffff		// base: 0, limit: 4 GB
	.long	0x00cf9200		// type: 32 bit, data read/write, privilege 0

	// real mode 16 bit code segment
	.long	0x0000ffff		// base: 0x10000, limit: 64 kB
	.long	0x00009e01
	// real mode 16 bit data and stack segment
	.long	0x0000ffff		// base: 0x10000, limit: 64 kB
	.long	0x00009201
	// real mode 16 bit stack segment
	.long	0x0000ffff		// base: 0, limit: 64 kB
	.long	0x00009200

gdt_descriptor:
	.word	0x2f			// 6 entries in the GDT (8 bytes each)
	.long	gdt

GLOBAL(gBootedFromImage):
	.byte	0

GLOBAL(gBootDriveID):
	.byte	0

GLOBAL(gBootPartitionOffset):
	.long	0

GLOBAL(gMultiBootInfo):
	.long	0

.org 1024

.section .bss
